package com.comunity_cloud.client.model;

import java.io.IOException;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.http.auth.AuthenticationException;

import com.comunity_cloud.client.model.communication.Host;
import com.comunity_cloud.client.model.fetch_file.FetchFilesClient;
import com.comunity_cloud.client.model.fetch_file.FetchFilesServer;
import com.comunity_cloud.client.model.file_list.FileListClient;
import com.comunity_cloud.client.model.file_list.FileListServer;
import com.comunity_cloud.client.model.upload_file.UploadFilesManager;
import com.comunity_cloud.exceptions.TransportException;
import com.comunity_cloud.rest.RestClient;
import com.user_hosted_storage.dto.ClientDTO;
import com.user_hosted_storage.dto.CommunityDTO;
import com.user_hosted_storage.dto.CommunityMemberDTO;

/**
 * @uml.dependency   supplier="com.comunity_cloud.client.model.PeerConnectionManager"
 */
public class MainModel2 {

	private static final long COMMUNITIES_UPDATE_INTERVAL = 30000;

	private static MainModel2 instance = new MainModel2();

	private Collection<CommunityDTO> communities = new LinkedList<CommunityDTO>();

	private List<CommunityModelInterface> communityModels = new LinkedList<CommunityModelInterface>();
	
	private Map<Long,CommunityMemberDTO> meInCommunities = new Hashtable<>();

	private long lastCommunitiesUpdate;

	private FetchFilesClient fetchFilesClient;

	private UploadFilesManager uploadFilesManager;

	private FileListClient fileListClient;
	
	private Map<Long, ClientDTO> clients = new Hashtable<>();
	
	private Map<String, ClientDTO> clientsByName = new Hashtable<>();

	private MainModel2() {

	}

	public static MainModel2 getInstance() {
		return instance;
	}

	public synchronized Collection<CommunityDTO> getCommunities() {

		if(System.currentTimeMillis() - lastCommunitiesUpdate > COMMUNITIES_UPDATE_INTERVAL) {
			try {
				CommunityDTO[] myCommunities = RestClient.getMyCommunities();
				communities.clear();
				clients.clear();
				clientsByName.clear();
				for (CommunityDTO communityDTO : myCommunities) {
					LinkedList<CommunityMemberDTO> members = communityDTO.getMembers();
					for (Iterator<CommunityMemberDTO> iterator = members.iterator(); iterator.hasNext();) {
						
						CommunityMemberDTO communityMemberDTO = (CommunityMemberDTO) iterator.next();
						ClientDTO client = clients.get(communityMemberDTO.getClient().getId());
						if(client == null) {
							clients.put(communityMemberDTO.getClient().getId(), communityMemberDTO.getClient());
							clientsByName.put(communityMemberDTO.getClient().getUserName(), communityMemberDTO.getClient());
						} else {
							communityMemberDTO.setClient(client);
						}
						if(communityMemberDTO.getClient().getId() == clientDTO.getId()) {
							iterator.remove();
							meInCommunities.put(communityDTO.getId(), communityMemberDTO);
						}
					}
					communities.add(communityDTO);
				}
				lastCommunitiesUpdate = System.currentTimeMillis();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return communities;
	}


	/**
	 * @uml.property  name="clientDTO"
	 * @uml.associationEnd  inverse="mainModel:com.user_hosted_storage.dto.ClientDTO"
	 */
	private ClientDTO clientDTO;

	private Host hostServer;

	/**
	 * Getter of the property <tt>clientDTO</tt>
	 * @return  Returns the clientDTO.
	 * @uml.property  name="clientDTO"
	 */
	public ClientDTO getClientDTO() {
		return clientDTO;
	}

	/**
	 * Setter of the property <tt>clientDTO</tt>
	 * @param clientDTO  The clientDTO to set.
	 * @uml.property  name="clientDTO"
	 */
	public void setClientDTO(ClientDTO clientDTO) {
		this.clientDTO = clientDTO;
		
	}

	public void register(String userName, String email, String password) {

	}

	public synchronized CommunityDTO getCommunity(long id) {
		for (CommunityDTO communityDTO : communities) {
			if(communityDTO.getId() == id) {
				return communityDTO;
			}
		}
		return null;
	}

	public synchronized ClientDTO getClient(long clientId) {
		return clients.get(clientId);
	}

	public List<CommunityMemberDTO> getOnlineHosts(CommunityDTO communityDTO) {
		return communityDTO.getMembers(); //TODO check availability
	}

	public FetchFilesClient getFetchFilesClient() {
		return fetchFilesClient;
	}

	public UploadFilesManager getUploadFilesManager() {
		return uploadFilesManager;
	}

	public FileListClient getFileListClient() {
		return fileListClient;
	}

	public void addCommunityModel(CommunityModelInterface communityModel){
		communityModels.add(communityModel);
	}

	public void loginWithHost() throws IOException, TransportException, AuthenticationException {
		hostServer = new Host();
		hostServer.start();
		clientDTO = RestClient.login();
		Configuration.getInstance().setUserName(clientDTO.getUserName());
		RestClient.setAcceptPort(hostServer.getPort());
		fetchFilesClient = new FetchFilesClient();
		uploadFilesManager = new UploadFilesManager();
		fileListClient = new FileListClient();
		new FetchFilesServer();
		new FileListServer();
		startPinholeThread();
	}
	
	public void loginWithoutHost() throws IOException, TransportException, AuthenticationException {
		clientDTO = RestClient.login();
		Configuration.getInstance().setUserName(clientDTO.getUserName());
		fetchFilesClient = new FetchFilesClient();
		uploadFilesManager = new UploadFilesManager();
		fileListClient = new FileListClient();
		startPinholeThread();
	}

	private void startPinholeThread() {
		Thread thread = new Thread() {
			
			public void run() {
				List<ClientDTO> clientsList = new LinkedList<>();
				while(true) {
					clientsList.clear();
					synchronized (MainModel2.this) {
						clientsList.addAll(clients.values());
					}
					for (ClientDTO clientDTO : clientsList) {
						try {
							hostServer.openPinhole(clientDTO.getIp(), clientDTO.getAcceptPort());
							hostServer.openPinhole(clientDTO.getIp(), clientDTO.getConnectPort());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
					try {
						Thread.sleep(5000);
					} catch (InterruptedException e) {}
				}
			}
		};
		thread.setDaemon(true);
	}

	public synchronized CommunityMemberDTO getCommunityMember(long communityId, long id) {
		for (CommunityDTO communityDTO : communities) {
			if(communityDTO.getId() == communityId) {
				List<CommunityMemberDTO> members = communityDTO.getMembers();
				for (CommunityMemberDTO communityMemberDTO : members) {
					if(communityMemberDTO.getClient().getId() == id) {
						return communityMemberDTO;
					}
				}
			}
		}
		if(id == clientDTO.getId()) {
			return meInCommunities.get(communityId);
		}
		return null;
	}

	public void updateClientPorts(String userName) {
		ClientDTO newClient = null;
		try {
			ClientDTO client = clientsByName.get(userName);
			newClient = RestClient.getClient(client.getId());
			client.setAcceptPort(newClient.getAcceptPort());
			client.setConnectPort(newClient.getConnectPort());
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TransportException e) {
			e.printStackTrace();
		}
	}
}
